package WSService

import (
	"encoding/json"
	"multimedia/conf"
	"multimedia/dbimpl"
	"multimedia/utils"
	"net/http"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/gorilla/websocket"
	log "github.com/sirupsen/logrus"
)

type envelope struct {
	session *Session
	data    *WSRecvReq
}
type ConferEvent struct {
	Flag     int //1：开始会议，2：关闭会议
	ConferId int64
	RoomId   string
	Creator  int64
}
type Hub struct {
	UpGrader    *websocket.Upgrader
	RecvData    chan *envelope
	ConferList  map[int64]*Conference
	SessionList map[int64]*Session
	Confev      chan *ConferEvent
	Mysql       *dbimpl.MysqlImpl
	Redis       *dbimpl.RedisImpl
	Lock        *sync.RWMutex
}

func New(mysql *dbimpl.MysqlImpl, redis *dbimpl.RedisImpl) *Hub {
	UpGrader := &websocket.Upgrader{
		ReadBufferSize:  2048,
		WriteBufferSize: 2048,
		CheckOrigin: func(r *http.Request) bool {
			return true
		},
	}
	hub := &Hub{
		UpGrader:    UpGrader,
		RecvData:    make(chan *envelope, 100),
		ConferList:  make(map[int64]*Conference, 50),
		SessionList: make(map[int64]*Session, 100),
		Confev:      make(chan *ConferEvent, 100),
		Mysql:       mysql,
		Redis:       redis,
		Lock:        new(sync.RWMutex),
	}
	go hub.Run()
	go hub.OnTimer()
	go hub.ConferCheckStart()
	go hub.ConferEventHandle()
	return hub
}
func (h *Hub) Run() {
	defer utils.HandlePanic()
	for s := range h.RecvData {
		h.ProcessCmd(s.session, s.data)
	}
}
func (h *Hub) OnTimer() {
	defer utils.HandlePanic()
	ticker := time.NewTicker(time.Duration(conf.ConfigFile.PingPeriod) * time.Second)
	defer ticker.Stop()
	for range ticker.C {
		h.timeTicker()
	}
}
func (h *Hub) ConferCheckStart() {
	defer utils.HandlePanic()
	ticker := time.NewTicker(1 * time.Minute)
	defer ticker.Stop()
	for range ticker.C {
		h.NotifyConferStart()
	}
}
func (h *Hub) delUserFromConfer(s *Session, uid int64) {
	conferId := s.ConferId()
	if conferId > 0 {
		confer, ok := h.ConferList[conferId]
		if !ok {
			log.WithField("func", "delUserFromConfer").Errorf("the confer is not exist %d", conferId)
			return
		}
		userLen, ConferType, ownerUid := confer.delUser(uid)
		log.WithField("func", "delUserFromConfer").Infof(" close the confer the id is:%d uid is:%d the user len is:%d,the confer owner is:%d the conferType is:%d",
			conferId, uid, userLen, ownerUid, ConferType)
		if userLen <= 0 && ConferType == 1 && ownerUid != 0 {
			owner := h.SessionList[ownerUid]
			if owner != nil {
				state := 0
				if confer.EndTime.Unix() < time.Now().Unix() {
					state = 2
				}
				h.NotifyConferState(owner, conferId, state)
			} else {
				log.WithField("func:", "delUserFromConfer").Errorf("NotifyConferState the creator is not exist %d the conferId is:%d", ownerUid, conferId)
			}
		}
		if userLen <= 0 {
			delete(h.ConferList, conferId)
			h.Confev <- &ConferEvent{2, conferId, "", ownerUid}
		}
	}
}
func (h *Hub) timeTicker() {
	h.Lock.Lock()
	defer h.Lock.Unlock()
	for _, session := range h.SessionList {
		overtime := session.IsOnTimer()
		if overtime {
			h.disconnect(session)
		}
	}
}
func (h *Hub) HandleRequest(w http.ResponseWriter, r *http.Request, userId string) error {
	defer utils.HandlePanic()
	conn, err := h.UpGrader.Upgrade(w, r, nil)
	if err != nil {
		log.WithField("func:", " wsclient HandleRequest ").Errorf("ws connect error:%s,%v", userId, err)
		return err
	}
	tokenRedisKey := utils.USER_LOGIN_TOKEN + userId
	res, e := h.Redis.GetKeyString(tokenRedisKey)
	if e != nil || res == "" {
		data := PacketResMsg(utils.ConnectRes, utils.UserNoLogin, nil)
		conn.WriteMessage(websocket.TextMessage, data)
		conn.Close()
		log.WithField("func:", " wsclient HandleRequest ").Errorf("ws connect the token err:%s,%s", userId, data)
		return nil
	}
	uid, _ := strconv.ParseInt(userId, 10, 64)
	session := &Session{
		conn:      conn,
		hub:       h,
		output:    make(chan *OutPutEnv, 100),
		ticker:    make(chan int, 1000),
		open:      true,
		Uid:       uid,
		checkTime: time.Now(),
	}
	h.connectHandler(session)
	go session.WritePump()
	session.ReadPump()
	session.Close(nil, utils.CloseMsg)
	h.Lock.Lock()
	defer h.Lock.Unlock()
	h.disconnect(session)
	return nil
}
func (h *Hub) disconnect(s *Session) {
	uid := s.uid()
	log.WithField("func", "disconnect").Tracef(" the session connect is disconnect %d", uid)
	delete(h.SessionList, uid)
	h.delUserFromConfer(s, uid)
}
func (h *Hub) connectHandler(session *Session) {
	h.Lock.Lock()
	defer h.Lock.Unlock()
	if conn, ok := h.SessionList[session.Uid]; ok {
		data := PacketResMsg(utils.NotifyLogOut, utils.LogOutReasonReplace, nil)
		conn.Close(data, utils.CloseMsg)
		h.disconnect(session)
	}
	h.SessionList[session.Uid] = session
	log.WithField("func", "connectHandler").Infof("connect success the uid is:%d", session.uid())
}
func (h *Hub) messageHandler(s *Session, p []byte) {
	var msg WSRecvReq
	err := json.Unmarshal(p, &msg)
	if err != nil {
		log.WithField("func", "messageHandler").Errorf("recv data err %v", err)
		return
	}
	h.RecvData <- &envelope{s, &msg}
}
func (h *Hub) GetSession(uid int64) *Session {
	h.Lock.RLock()
	defer h.Lock.RUnlock()
	user, ok := h.SessionList[uid]
	if !ok {
		return nil
	}
	return user
}

func (h *Hub) ProcessCmd(s *Session, msg *WSRecvReq) {
	switch msg.Cmd {
	case utils.UpdateDeviceStateReq:
		h.UpdateDeviceState(s, msg.Data, msg.ConferId)
	case utils.PingReq:
		s.UpdateTime()
	case utils.UpdateUserNameReq:
		h.UpdateUserName(s, msg.Data, msg.ConferId)
	case utils.UserLogoutConferReq:
		h.UserLogoutConfer(s, msg.ConferId)
	case utils.UserEnterConferReq:
		h.UserEnterConfer(s, msg.Data, msg.ConferId)
	case utils.KickUserReq:
		h.KickUser(s, msg.Data, msg.ConferId)
	case utils.CloseShareDesktopReq:
		h.CloseShareDesktop(s, msg.Data, msg.ConferId)
	case utils.WhiteBoardReq:
		h.WhiteBoard(s, msg.Data, msg.ConferId)
	case utils.CloseWhiteBoardReq:
		h.CloseWhiteBoard(s, msg.Data, msg.ConferId)
	case utils.CloseConferReq:
		h.CloseConfer(msg.ConferId)
	case utils.StartRecordReq:
		h.UserStartRecord(s, msg.Data, msg.ConferId)
	case utils.AckStartRecordReq:
		h.AckStartRecord(s, msg.Data, msg.ConferId)
	case utils.AckStartRecordRes:
		h.AckStartRecordRes(s, msg.Data, msg.ConferId)
	case utils.TransferModeratorReq:
		h.TransferModeratorReq(s, msg.Data, msg.ConferId)
	case utils.NotifyTextMessage:
		h.SendTextMessageReq(s, msg.Data, msg.ConferId)
	case utils.NotifyUserTextMessage:
		h.SendUserTextMessageReq(s, msg.Data, msg.ConferId)
	}
}
func (h *Hub) SendUserTextMessageReq(s *Session, p string, conferId int64) {
	room := h.GetConference(conferId)
	if room == nil {
		return
	}
	room.SendUserTextMessage(s, p)
}
func (h *Hub) SendTextMessageReq(s *Session, p string, conferId int64) {
	room := h.GetConference(conferId)
	if room == nil {
		return
	}
	room.SendTextMessage(s, p)
}
func (h *Hub) TransferModeratorReq(s *Session, p string, conferId int64) {
	room := h.GetConference(conferId)
	if room == nil {
		return
	}
	room.TransferModerator(s, p)
	//保存主持人id
	comm := &WSEnterConferReq{}
	err := json.Unmarshal([]byte(p), &comm)
	if err != nil {
		log.WithField("func:", "TransferModeratorReq").Infof("unmarshal the msg error :%v", err)
	}
	h.Mysql.EditConferModerator(comm.Uid, conferId)
}
func (h *Hub) GetConference(conferId int64) *Conference {
	h.Lock.RLock()
	defer h.Lock.RUnlock()
	if room, ok := h.ConferList[conferId]; ok {
		return room
	}
	return nil
}
func (h *Hub) UpdateUserName(s *Session, p string, conferId int64) {
	UserNameReq := WSUpdateUserNameReq{}
	err := json.Unmarshal([]byte(p), &UserNameReq)
	if err != nil {
		log.WithField("func:", "UpdateUserName").Errorf("the data is err:%s", err.Error())
		return
	}
	room := h.GetConference(conferId)
	if room == nil {
		log.WithField("func:", "WebService UpdateUserName").Errorf(" the conference %d is not exist", conferId)
		return //处理错误
	}
	room.UpdateUserName(s, &UserNameReq)
}
func (h *Hub) UserLogoutConfer(s *Session, conferId int64) {
	h.Lock.RLock()
	defer h.Lock.RUnlock()
	h.delUserFromConfer(s, s.uid())
	s.SetConferId(0)
}

func (h *Hub) KickUser(s *Session, p string, conferId int64) {
	commdata := WSCommonReq{}
	json.Unmarshal([]byte(p), &commdata)
	room := h.GetConference(conferId)
	if room == nil {
		log.WithField("func:", "WebService KickUser").Errorf(" the conference %d is not exist", conferId)
		return
	}
	room.KickUser(s, &commdata)
}
func (h *Hub) CloseShareDesktop(s *Session, p string, conferId int64) {
	commdata := WSCommonReq{}
	json.Unmarshal([]byte(p), &commdata)
	room := h.GetConference(conferId)
	if room == nil {
		log.WithField("func:", "WebService CloseShareDesktop").Errorf(" the conference %d is not exist", conferId)
		return
	}
	room.CloseShared(s, &commdata)
}

func (h *Hub) NotifyConferState(owner *Session, conferId int64, state int) {
	packet := NotifyUserConferState{
		ConferId: conferId,
		State:    state,
	}
	log.WithField("func:", "NotifyConferState ").Errorf(" the conference %d the state is:%d,notify uid is:%d", conferId, state, owner.uid())
	data, _ := json.Marshal(packet)
	msg := PacketResMsg(utils.NotifyUserConferState, utils.Success, data)
	owner.SendMessage(msg, utils.NotifyUserConferState)
}

func (h *Hub) UpdateDeviceState(s *Session, p string, conferId int64) {
	room := h.GetConference(conferId)
	if room == nil {
		log.WithField("func:", "WebService UpdateDeviceState").Errorf(" the conference %d is not exist", conferId)
		return //TODO 会议不存在
	}
	DeviceStateReq := WSUpdateDeviceStateReq{}
	err := json.Unmarshal([]byte(p), &DeviceStateReq)
	if err != nil {
		log.WithField("func:", "UpdateUserName").Errorf("the data is err:%s", err.Error())
		return
	}
	room.UpdateDeviceState(s, &DeviceStateReq)
}
func (h *Hub) WhiteBoard(s *Session, p string, conferId int64) {
	room := h.GetConference(conferId)
	if room == nil {
		log.WithField("func:", "WebService WhiteBoard").Errorf(" the conference %d is not exist", conferId)
		return
	}
	room.StartWhiteBoard(s, p)
	h.Mysql.UpdateWhiteBoardFlag(conferId, 1)
}
func (h *Hub) CloseWhiteBoard(s *Session, p string, conferId int64) {
	room := h.GetConference(conferId)
	if room == nil {
		return
	}
	room.CloseWhiteBoard(s, p)
	h.Mysql.UpdateWhiteBoardFlag(conferId, 0)

}

func (h *Hub) AckStartRecord(s *Session, p string, conferId int64) {
	room := h.GetConference(conferId)
	if room == nil {
		return
	}
	room.AckStartRecord(s, p)
}
func (h *Hub) AckStartRecordRes(s *Session, p string, conferId int64) {
	room := h.GetConference(conferId)
	if room == nil {
		return
	}
	room.ForwardMsg(s, p)
}
func (h *Hub) UserEnterConfer(s *Session, p string, conferId int64) {
	conference := h.GetConference(conferId)
	if conference == nil {
		confInfo := h.Mysql.GetConferById(conferId)
		if confInfo == nil {
			return
		}
		creator, _ := strconv.ParseInt(confInfo.Creator, 10, 64)
		conference = &Conference{
			usersList: make(map[int64]*Session, 10),
			Id:        confInfo.Id,
			Type:      confInfo.Type,
			Creator:   creator,
			EndTime:   utils.FormatAsTime(confInfo.EndTime),
		}
		h.InsertConfer(conference)
		h.Confev <- &ConferEvent{1, conferId, confInfo.RoomId, creator}
		if conference.Type == 1 {
			OwnSession := h.GetSession(conference.Creator)
			if OwnSession != nil {
				h.NotifyConferState(OwnSession, conferId, 1)
			} else {
				log.WithField("func:", "UserEnterConfer").Errorf("NotifyConferState the creator is not exist %d the conferId is:%d", conference.Creator, conferId)
			}
		}
	}
	var EnterConferReq WSEnterConferReq
	json.Unmarshal([]byte(p), &EnterConferReq)
	conference.UserEnterConfer(s, EnterConferReq.Uid)
	s.SetConferId(conferId)
	log.WithField("func:", "UserEnterConfer").Infof("the user:%d enter conference is:%v the conferid is %d", s.Uid, conference, conferId)
}

func (h *Hub) UserStartRecord(s *Session, p string, conferId int64) {
	conference := h.GetConference(conferId)
	if conference == nil {
		log.WithField("func:", "WebService UserStartRecord").Errorf(" the conference %d is not exist", conferId)
		return
	}
	conference.NotifyUserStartRecord(s, p)
}

func (h *Hub) InsertConfer(confer *Conference) {
	h.Lock.Lock()
	defer h.Lock.Unlock()
	h.ConferList[confer.Id] = confer
}
func (h *Hub) IsExist(uid int64) bool {
	h.Lock.RLock()
	defer h.Lock.RUnlock()
	if _, ok := h.SessionList[uid]; ok {
		return true
	}
	return false
}

func (h *Hub) CloseConfer(conferId int64) {
	h.Lock.Lock()
	defer h.Lock.Unlock()
	if confer, ok := h.ConferList[conferId]; ok {
		delete(h.ConferList, conferId)
		for _, v := range confer.usersList {
			v.SetConferId(0)
		}
		if confer.Type == 1 && confer.Creator != 0 {
			owner := h.SessionList[confer.Creator]
			if owner != nil {
				state := 0
				if confer.EndTime.Unix() < time.Now().Unix() {
					state = 2
				}
				h.NotifyConferState(owner, conferId, state)
			} else {
				log.WithField("func:", "CloseConfer").Errorf("NotifyConferState the creator is not exist %d the conferId is:%d", confer.Creator, conferId)
			}
		}
		h.Confev <- &ConferEvent{2, conferId, "", confer.Creator}
	} else {
		log.WithField("func:", "CloseConfer").Errorf("the confer is not exist the conferId is:%d", conferId)
	}
}

// 会议即将开始通知
func (h *Hub) NotifyConferStart() {
	confeList := h.Mysql.GetNotifyConferList(360)
	if confeList != nil && len(*confeList) > 0 {
		for _, value := range *confeList {
			confeId := value.Id
			confeIdStr := strconv.FormatInt(confeId, 10)
			redisKey := utils.CONFE_WILL_START + confeIdStr
			retval, err := h.Redis.GetKeyString(redisKey)
			if err != nil {
				log.WithField("Func", "NotifyConferStart").Warnf("get confer_will_start redis fail", err)
			}
			if retval != "" {
				continue

			} else {
				err = h.Redis.UpdateKey(redisKey, value.RoomId, 10*60)
				if err != nil {
					log.WithField("Func", "NotifyConferStart").Errorf("put confer_will_start redis error", err)
					continue
				}
			}
			userId := value.CreatorId
			room := h.GetConference(confeId)
			if room != nil {
				session := room.GetUser(userId)
				if session != nil {
					log.WithField("Func", "NotifyConferStart").Infof("Creator already in the meeting not send Notify,CreatorId is :%v", userId)
					continue
				}
			}
			data, _ := json.Marshal(value)
			msg := PacketResMsg(utils.NotifyConferComingSoon, utils.Success, data)
			owner := h.SessionList[userId]
			if owner != nil {
				log.WithField("Func", "NotifyConferStart").Infof(" meeting Notify is send to :%v", userId)
				owner.SendMessage(msg, utils.NotifyConferComingSoon)
			}
		}
	}
}

// 录制结束通知
func (h *Hub) NotifyRecEnded(roomInfoId int64, roomId string) {
	confe := h.Mysql.GetConfeByRoomId(roomId)
	roomInfo := h.Mysql.GetRoomInfoById(roomInfoId)
	record := roomInfo.Record
	if record != "" {
		recSlice := strings.Split(record, "#")
		log.WithField("Func", "NotifyRecEnded").Warnf("the recslice len is:%d,%d,%d", len(recSlice), confe.RecordCount, roomInfo.State)
		if len(recSlice) == confe.RecordCount && roomInfo.State == 2 {
			UId := confe.Creator
			confe.PlayUrl = recSlice
			data, _ := json.Marshal(confe)
			msg := PacketResMsg(utils.NotifyRecordUrl, utils.Success, data)
			owner := h.SessionList[UId]
			log.WithField("Func", "NotifyRecEnded").Warnf("userId is: %d, owner is %v", UId, owner)
			if owner != nil {
				owner.SendMessage(msg, utils.NotifyRecordUrl)
				var Tiding utils.TidingsPO
				Tiding.Type = 0
				Tiding.BizId = roomId
				Tiding.UserId = UId
				Tiding.Content = string(msg)
				h.Mysql.AddTidingsInfo(&Tiding)
			}
		}
	}
}

func (h *Hub) ConferEventHandle() {
	for ev := range h.Confev {
		if ev.Flag == 2 {
			h.CloseConferHanlde(ev.ConferId)
		} else if ev.Flag == 1 {
			h.CreateConferHandler(ev.ConferId, ev.RoomId, ev.Creator)
		}
	}
}
func (h *Hub) CloseConferHanlde(conferId int64) {
	conferInfo := h.Mysql.GetConferById(conferId)
	timestamp := time.Now().UnixMicro()
	h.Mysql.CloseRoomByRoomId(conferInfo.RoomId, utils.TERMINATED, timestamp)
	_, roomInfo := h.Mysql.GetRoomInfoByAppIdAndRoomId(conf.ConfigFile.SdkAppId, conferInfo.RoomId)

	log.WithField("Func", "CloseConferHanlde").Warnf("close meeting %d the roomid is:%s", conferInfo.Id, conferInfo.RoomId)
	h.NotifyRecEnded(roomInfo.Id, conferInfo.RoomId)
}
func (h *Hub) CreateConferHandler(conferId int64, RoomId string, creator int64) {
	// roomInfo := h.Mysql.GetRoomInfoByRoomId(RoomId)

}
